package ace.cmp.data.methodname.sql.mybatisplus.core.mysql.dml;

import ace.cmp.data.methodname.method.name.sql.core.enums.LogicalOpEnum;
import ace.cmp.data.methodname.method.name.sql.core.enums.RelationalOpEnum;
import ace.cmp.data.methodname.method.name.sql.entity.model.EntityMethodNameToQueryFilter;
import ace.cmp.data.methodname.method.name.sql.entity.model.EntityMethodNameToQuerySort;
import ace.cmp.data.methodname.method.name.sql.entity.model.EntityPropertyMapping;
import ace.cmp.data.methodname.method.name.sql.entity.resolver.EntityMethodNameToQueryResolver;
import ace.cmp.data.methodname.sql.mybatisplus.core.properties.MybatisPlugMethodNameToSqlProperties;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;

/**
 * @author caspar
 * @date 2023/2/24 10:06 根据方法解析元数据，生成对应的sql
 */
public abstract class AbstractMethodToDmlSqlBuilder {
  private final EntityMethodNameToQueryResolver queryResolver;
  private final TableInfo tableInfo;
  private final MybatisPlugMethodNameToSqlProperties sqlProperties;
  private final SQL sqlBuilder = new SQL();
  private Integer filterIndex = 0;

  public AbstractMethodToDmlSqlBuilder(
      EntityMethodNameToQueryResolver queryResolver,
      TableInfo tableInfo,
      MybatisPlugMethodNameToSqlProperties sqlProperties) {
    this.queryResolver = queryResolver;
    this.tableInfo = tableInfo;
    this.sqlProperties = sqlProperties;
  }

  /**
   * 生成sql
   *
   * @return null或空代表该类型元数据，不是该类负责生成sql
   */
  public abstract String getSql();

  /**
   * 是否负责该类型元数据生成sql
   *
   * @return
   */
  public abstract Boolean getSupport();

  protected EntityMethodNameToQueryResolver getQueryResolver() {
    return this.queryResolver;
  }

  protected MybatisPlugMethodNameToSqlProperties getSqlProperties() {
    return this.sqlProperties;
  }

  protected TableInfo getTableInfo() {
    return this.tableInfo;
  }

  protected SQL getSqlBuilder() {
    return this.sqlBuilder;
  }

  protected void addSelects() {
    List<EntityPropertyMapping> selectProperties = queryResolver.getProperties();
    Boolean isDistinct = queryResolver.getDistinct();
    for (int i = 0; i < selectProperties.size(); i++) {
      EntityPropertyMapping propertyMapping = selectProperties.get(i);
      if (i == 0 && isDistinct) {
        sqlBuilder.SELECT_DISTINCT(propertyMapping.getColumn());
      } else {
        sqlBuilder.SELECT(propertyMapping.getColumn());
      }
    }
  }

  protected void addSorts() {
    this.queryResolver.getSorts().stream().forEach(querySort -> this.addSort(querySort));
  }

  protected void addSort(EntityMethodNameToQuerySort sort) {
    sqlBuilder.ORDER_BY(
        String.format(
            "%s %s", sort.getPropertyMapping().getColumn(), sort.getAsc() ? "ASC" : "DESC"));
  }

  protected void addSelectFrom() {
    sqlBuilder.FROM(this.tableInfo.getTableName());
  }

  protected void addFilters() {
    this.queryResolver.getFilters().stream().forEach(queryFilter -> this.addFilter(queryFilter));
  }

  protected void addFilter(EntityMethodNameToQueryFilter queryFilter) {
    if (queryFilter.getLogicalOp().equals(LogicalOpEnum.AND)) {
      sqlBuilder.AND();
    } else {
      sqlBuilder.OR();
    }
    if (RelationalOpEnum.AFTER.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s > %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.BEFORE.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s < %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.CONTAINS.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s LIKE CONCAT('%%',%s,'%%')",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.BETWEEN.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s BETWEEN %s AND %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping()),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.ENDING_WITH.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s LIKE CONCAT(%s,'%%')",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.FALSE.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(String.format("%s == 0", queryFilter.getPropertyMapping().getColumn()));
    } else if (RelationalOpEnum.GREATER_THAN.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s > %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.GREATER_THAN_EQUAL.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s >= %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.IN.equals(queryFilter.getRelationalOp())) {
      String originName = this.getMybatisParamName(queryFilter.getPropertyMapping(), true);
      sqlBuilder.WHERE(
          String.format(
              """
%s IN <foreach item="item" collection="%s" separator="," open="(" close=")" index="i">#{item}</foreach>
""",
              queryFilter.getPropertyMapping().getColumn(), originName));
    } else if (RelationalOpEnum.EQUALS.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s = %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.IS_EMPTY.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format("(%s == '' || %s IS NULL)", queryFilter.getPropertyMapping().getColumn()));
    } else if (RelationalOpEnum.IS_NOT_EMPTY.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "(%s != '' || %s IS NOT NULL)", queryFilter.getPropertyMapping().getColumn()));
    } else if (RelationalOpEnum.IS_NOT_NULL.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format("%s IS NOT NULL", queryFilter.getPropertyMapping().getColumn()));
    } else if (RelationalOpEnum.IS_NULL.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(String.format("%s IS NULL", queryFilter.getPropertyMapping().getColumn()));
    } else if (RelationalOpEnum.LESS_THAN.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s < %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.LESS_THAN_EQUAL.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s <= %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.LIKE.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s LIKE %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.NOT.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s != %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.NOT_IN.equals(queryFilter.getRelationalOp())) {
      String originName = this.getMybatisParamName(queryFilter.getPropertyMapping(), true);
      sqlBuilder.WHERE(
          String.format(
              """
%s NOT IN <foreach item="item" collection="%s" separator="," open="(" close=")" index="i">#{item}</foreach>
""",
              queryFilter.getPropertyMapping().getColumn(), originName));

    } else if (RelationalOpEnum.NOT_LIKE.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s NOT LIKE %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.STARTING_WITH.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(
          String.format(
              "%s LIKE CONCAT('%%',%s)",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    } else if (RelationalOpEnum.TURE.equals(queryFilter.getRelationalOp())) {
      sqlBuilder.WHERE(String.format("%s == 1)", queryFilter.getPropertyMapping().getColumn()));
    } else {
      // 默认使用等于过滤
      sqlBuilder.WHERE(
          String.format(
              "%s = %s",
              queryFilter.getPropertyMapping().getColumn(),
              this.getMybatisParamName(queryFilter.getPropertyMapping())));
    }
  }

  protected void addLimit() {
    Optional<Integer> topNumberOptional = this.getQueryResolver().getTopNumber();
    if (topNumberOptional.isPresent()) {
      this.sqlBuilder.LIMIT(topNumberOptional.get());
    }
  }

  protected String getMybatisParamName(EntityPropertyMapping propertyMapping) {
    return this.getMybatisParamName(propertyMapping, false);
  }

  protected String getMybatisParamName(
      EntityPropertyMapping propertyMapping, Boolean isOriginName) {
    String paramName = filterIndex.toString();
    List<Parameter> parameters = Arrays.asList(this.queryResolver.getMethod().getParameters());
    Parameter parameter = parameters.get(filterIndex);
    if (parameter != null) {
      Param paramAnnotation = parameter.getAnnotation(Param.class);
      if (paramAnnotation != null) {
        paramName = paramAnnotation.value();
      }
    }

    String result = isOriginName ? paramName : String.format("#{%s}", paramName);
    filterIndex++;
    return result;
  }
}
